﻿import base = require("../base");
import aop = require("../aop");
import client = require("./client");


//****************** 常量 **********************
/**
 * 模块名称
 */
export const moduleName = "kf.clientApp";
/**
 * 超时时间（毫秒）
 */
const timeout = 500;
/**
 * 提示：程序加载
 */
const loadingTip = "程序加载中...";

//****************** 路由模块 ******************
/**
 * 路由
 */
@aop.addon("路由信息", "前端路由信息", moduleName)
export class kfRouter extends aop.baseObject {
    /** 
     * hash 表
     * key：路由
     */
    routerList: string[];
    /** 主页/首页 */
    index: string;
    /** 回调 */
    callback: Function;
    /**
     * 构造器
     */
    constructor() {
        super();
        // 默认首页
        this.index = "home";
        this.routerList = [];
    }
}

/**
 * 路由模块
 */
export class route {

    /**
     * 标签:路由
     */
    static routeTag = "#";

    /**
     * 路由器
     */
    static routers: kfRouter;

    /**
     * 添加路由
     * @param router 路由
     * @param callback 回调函数，调用回调函数的时候同时也会传入相应参数
     * @remark 如果路由已经存在则会覆盖
     */
    static register(router: string) {
        this.routers.routerList.push(router);
    }

    /**
     * 删除路由
     * @param addr 地址
     */
    static remove(router: string) {
        // 判断是否存在
        if (this.routers.routerList[router]) {
            delete this.routers.routerList[router];
        }
    }

    /**
     * 跳转到页面
     * @param addr 地址
     */
    static go(addr: string) {
        window.location.hash = addr;
    };
    /**
     * 设置主页
     * @param index 主页
     */
    static setIndex(index: string) {
        this.routers.index = index;
    };

    /**
     * 重载页面
     */
    static reload() {
        var hash = window.location.hash.replace(this.routeTag, '');
        // 验证
        // 验证失败跳转首页
        var match = this.getMatch(hash);
        if (match) {
            var argus = hash.split('/');
            this.routers.callback.apply(this, argus);
        } else {
            this.routers.index && this.go(this.routers.index);
        }
    };

    /**
     * 开始路由
     * @remark 实际上只是为了当直接访问路由路由地址的时候能够及时调用回调
     * 添加监听浏览器hashchange 以及dom loaded函数
     */
    static start() {
        /** 路由监听事件 */
        window.addEventListener('hashchange', function () {
            route.reload();
        });
    }

    /**
     * 验证地址
     * @param hash 地址hash
     * @return true or false
     */
    private static getMatch(hash: string): boolean {
        // TODO
        // 遍历路由规则表
        // 对没有路由规则初始化正则
        // 用生成的正则匹配地址hash
        // 如果匹配成功直接返回
        // 否则返回失败
        return true;
    }

    /**
     * 初始化正则
     * @param route 路由
     */
    private static initRegx(router: string): RegExp {
        // 第一个正则 匹配诸如/,.+-?$#{ } []] 关键字  并在关键字前面加转译字符\
        // 第二个正则 匹配() 标示()内部内容可有可无
        // 第三个正则 匹配: 在:后面可以由接受任意字符,直到遇到下一个/
        // 第四个正则 匹配* 在*后面可以由接受任意字符
        router = router.replace(/[/,.+\-?$#{}\[\]]/g, '\\$&')
            .replace(/\((.*?)\)/g, '(?:$1)?')
            .replace(/(\/\w?:\w+)+/g, '\/([^/]+)')
            .replace(/\*\w*/g, '([^?]*?)');
        return new RegExp('^' + router + '$');
    }
}

//****************** 模板模块 **********************

/**
 * 视图基类
 */
@aop.addon("视图基类", "视图基类", moduleName)
export class viewObject extends aop.baseObject {
    /**
     * 绑定数据事件
     * @param data 数据
     */
    bindingHandler?(data: any): any { }
}

/**
 * 布局基类
 */
@aop.addon("布局基类", "所有应用框架对应布局的基类", moduleName)
export class layoutObject extends viewObject {
    /** 导航 */
    navigators: command[];
}

/**
 * 命令
 */
@aop.addon("命令", "所有导航、附加导航等命令的对象", moduleName)
export class command extends aop.baseObject {
    /** 标题 */
    title?: string;
    /** 执行动作 */
    onExcute?: string;
    /** 图标 */
    icon?: string;
    /** 图标 */
    imageUrl?: string;
    /** 显示图标 */
    showIcon?: boolean;
}

/**
 * 模板类
 */
@aop.addon("模板类", "模板信息", moduleName)
export class template extends aop.baseObject {
    /** 目录 */
    dir: string;
    /** 文件名 */
    fileName: string;
    /** html是否加载 */
    isLoadHtml: boolean;
    /** html */
    html: string;
    /** 是否包含js */
    hasJS?: boolean;
    /** 是否包含css */
    hasCss?: boolean;

    /** 是否显示 */
    isShow: boolean;
    /** js是否加载 */
    isLoadJs: boolean;
    /** 是否绑定 */
    isBind: boolean;
    /** 视图 */
    views: { [id: string]: viewObject };

    /**
     * 构造函数
     */
    constructor() {
        super();
        this.isLoadHtml = false;
        this.isShow = false;
        this.isLoadJs = false;
        this.isBind = false;
        this.hasJS = true;
        this.hasCss = true;
    }
}

/**
 * 模板加载器
 */
export class templateLoader {
    /** 模板缓存 */
    private static _templateCache: { [name: string]: template } = {};

    /** 当前视图名称 */
    private static currentViewName: string;

    /**
     * 获取模板
     * @param name 模板名称
     * @returns 模板
     */
    static getTemplate(name: string) {
        if (this._templateCache) {
            if (this._templateCache[name]) {
                return this._templateCache[name];
            }
            else {
                throw new Error(`找不到模板`);
            }
        }
        return undefined;
    }

    /**
     * 设置模板
     * @param name 模板名称
     * @param temp 模板
     */
    static setTemplate(name: string, temp: template) {
        this._templateCache[name] = temp;
    }

    /**
     * 模板html追加
     * @param name 模板名称
     * @param isLayout 是否布局
     * @remark 一个应用只能且仅能配置一个布局，多个布局情况当作视图处理
     */
    static loadTemplateView(name: string, id: string, isLayout: boolean = false, args: any[]) {
        if (!isLayout) {
            // 当前加载的视图名称和当前视图id是否相同
            if (this.currentViewName == name) {
                return;
            }
        }
        // 模板
        let temp = this.getTemplate(name);
        if (temp) {
            // 模板是否加载
            if (!temp.isLoadHtml) {
                let htmlUrl = `${temp.dir}/${temp.fileName}.html`;
                let cssUrl = `${temp.dir}/${temp.fileName}.css`;
                // 加载样式
                if (temp.hasCss) {
                    client.loadStyle(cssUrl);
                }
                // 获取html
                client.getTemplate(htmlUrl, (html: string) => {
                    temp.html = html;
                    temp.isLoadHtml = true;
                });
            }
            // 循环模板html是否获取
            let interval = setInterval(() => {
                if (temp.isLoadHtml) {
                    // 追加布局html
                    if (isLayout) {
                        $('body').append(temp.html);
                    } else {
                        // 指定内容div的id
                        let contentID = $(temp.html).attr("id");
                        // 指定内容html
                        let contentHtml = $(temp.html)[0].children[0].outerHTML;
                        var content = $(`#${contentID}`);
                        // 清空div内容
                        content.empty();
                        // 追加html
                        $(contentHtml).appendTo(content);
                    }
                    temp.isShow = true;
                    clearInterval(interval);
                }
            }, timeout);
            this.bindTo(name, id, args);
            // 设置当前模板视图
            if (!isLayout) {
                this.setCurrentTemplateView(name);
            }
        }
    }

    /**
     * 设置当前模板视图
     * @param name
     * @param id
     */
    private static setCurrentTemplateView(name: string) {
        let currentTemp = this.getTemplate(name);
        currentTemp.isShow = false;
        currentTemp.isBind = false;
        this.currentViewName = name;
    }

    /**
     * 模板绑定
     * @param name 模板名称
     * @param id 视图id
     */
    private static bindTo(name: string, id: string, args: any[]) {
        // 模板
        let temp = this.getTemplate(name);
        if (temp) {
            if (!temp.hasJS) {
                temp.isLoadJs = true;
            }
            // 是否有js并且加载js
            if (!temp.isLoadJs && temp.hasJS) {
                // 动态加载的js都是相对根目录而言
                // 在此处动态加载js时
                // 相对当前文件目录，加载的目录则要添加../../指定到根目录
                let path = `../../${temp.dir}/${temp.fileName}`;
                require(path);
                temp.isLoadJs = true;
            }
            aop.fetchAddonProtoType(temp.views);
            // 绑定视图实例
            if (temp.views[id]) {
                // 循环视图html是否追加
                // 通过ko.applyBindings绑定
                let interval = setInterval(() => {
                    if (temp.isShow && !temp.isBind && temp.isLoadJs) {
                        temp.isBind = true;
                        temp.views[id].bindingHandler(args);
                        clearInterval(interval);
                    }
                }, timeout);
            }
            else {
                throw new Error(`找不到视图`);
            }
        }
    }
}

//****************** 应用模块 **********************
/**
 * 模块设置
 * @remark 文件名称规则：html、javascript、css文件名称统一相同
 */
@aop.addon("模块设置", "模块设置信息", moduleName)
export class templateSetting extends aop.baseObject {
    /** 目录 */
    dir?: string;
    /** 文件名称 */
    fileName?: string;
    /** 是否包含js */
    hasJS?: boolean;
    /** 是否包含css */
    hasCss?: boolean;
    /** 模板实例 */
    instances?: { [id: string]: viewObject };
}

/**
 * 客户端应用设置
 */
@aop.addon("客户端应用设置", "包括应用的路由，模板，实体等信息", moduleName)
export class clientAppSetting extends aop.appSetting {
    /** 路由 */
    routers?: string[];
    /** 模板 */
    templates?: { [name: string]: templateSetting }
    /** 布局 */
    layout?: string;
    /** 首页 */
    index?: string;
}

/**
 * 应用
 */
@aop.addon("应用", "包含应用程序所有信息，路由，导航，附加功能等", moduleName)
export class application extends aop.baseObject {
    /** 全局配置 */
    public static _gobalSetting: clientAppSetting = {};

    /**
     * 构造函数
     */
    constructor(setting: clientAppSetting) {
        super();
        this._gobalSetting = setting;
        // 路由器
        route.routers = new kfRouter();
        // 设置路由回调
        route.routers.callback = this.callback;
    }
    /**
     * 启动
     */
    navigate(): void {
        let setting = this._gobalSetting;
        let templates = setting.templates;
        // 设置模板
        if (templates) {
            for (let name in templates) {
                let temp = new template();
                temp.dir = templates[name].dir;
                temp.fileName = templates[name].fileName;
                temp.hasCss = templates[name].hasCss;
                temp.hasJS = templates[name].hasJS;
                temp.views = templates[name].instances;
                templateLoader.setTemplate(name, temp);
            }
        }
        // 设置路由
        if (setting.routers) {
            for (let router of setting.routers) {
                route.register(router);
            }
            route.start();
        }
        // 设置路由主页
        if (setting.index) {
            route.setIndex(setting.index);
        }
        this.loadApplication();
    }

    /**
     * 加载程序
     */
    private loadApplication(): void {
        start({ loadingTips: loadingTip });
        // 加载布局
        this.loadLayout();
        // 加载视图
        this.loadView();
    }
    /**
     * 加载视图
     */
    private loadView(): void {
        let layoutName = this._gobalSetting.layout;
        let router = this._gobalSetting.index;
        let argus = router.split("#")[1].split("/");
        let viewName = argus[0], viewID = argus[1];
        let interval = setInterval(() => {
            var layoutInfo = templateLoader.getTemplate(layoutName);
            if (layoutInfo.isShow) {
                // 首页
                templateLoader.loadTemplateView(viewName, viewID, false, undefined);
                clearInterval(interval);
            }
        }, timeout);
        // 结束loading效果
        let intervalPage = setInterval(() => {
            var viewInfo = templateLoader.getTemplate(viewName);
            if (viewInfo.isShow && viewInfo.isBind) {
                end();
                clearInterval(intervalPage);
            }
        }, timeout);
    }

    /**
     * 加载布局
     */
    private loadLayout(): void {
        let name = this._gobalSetting.layout;
        let temp = templateLoader.getTemplate(name);
        let layoutID: string = "";
        for (let id in temp.views) {
            layoutID = id;
            break;
        }
        if (layoutID === "") {
            throw new Error("加载程序失败，程序布局不存在");
        }
        // 追加布局模板
        templateLoader.loadTemplateView(name, layoutID, true, undefined);
        this.currentLayoutName = name;
    }

    /**
     * 路由回调函数
     */
    private callback(): void {
        var args = arguments;
        // 存在参数，根据参数跳转页面
        if (args && args.length > 0) {
            var viewName = args[0],viewID = "";
            if (viewName == "") {
                route.go(route.routers.index);
                return;
            }
            // 视图信息
            var viewInfo = templateLoader.getTemplate(viewName);
            if (viewInfo) {
                start({ loadingTips: loadingTip });
                // 视图实例信息
                var viewInstrance = viewInfo.views;
                // 没有指定视图实例ID，只有视图名称
                if (args.length == 1) {
                    var view = viewInstrance[0];
                    viewID = view.id;
                } else {
                    viewID = args[1];
                }
                var params = getViewParam(args);
                params.shift();
                templateLoader.loadTemplateView(viewName, viewID, false, params);
                let interval = setInterval(() => {
                    var viewInfo = templateLoader.getTemplate(viewName);
                    if (viewInfo.isShow && viewInfo.isBind) {
                        end();
                        clearInterval(interval);
                    }
                }, timeout);
            }
        } else {
            route.go(route.routers.index);
        }
    }
}

/**
 * 视图参数
 * @param args 方法变量
 * @returns 参数数组
 */
export function getViewParam(args: IArguments) {
    var params: any = [];
    // IArguments不是数组，必须使用each方法遍历
    $(args).each(function (index, arg) {
        params.push(arg);
    });
    // 删除第一个元素
    params.shift();
    return params;
}

/**
 * 绑定视图模型
 * @param viewModel 视图模型
 * @param node 绑定节点
 */
export function applyBindings(viewModel: any, node: Element) {
    // 防止绑定相同节点多次，清除节点
    ko.cleanNode(node);
    // 绑定视图模型到指定节点
    ko.applyBindings(viewModel, node);
}

//****************** loading 效果 ******************
/**
 * 开始
 * @param option 加载画面配置
 */
export function start(options: any) {
    // 默认配置
    var defaults = {
        //loading页面透明度
        opacity: 100,
        //loading页面背景色
        backgroundColor: "#fff",
        //提示边框颜色
        borderColor: "#bbb",
        //提示边框宽度
        borderWidth: 1,
        //提示边框样式
        borderStyle: "solid",
        //提示文本
        loadingTips: "Loading, please wait...",
        //提示颜色
        TipsColor: "#666",
        //页面加载完成后，加载页面渐出速度
        delayTime: 1000,
        //loading页面层次
        zindex: 999,
        //设置挂起,等于0时则无需挂起
        sleep: 0
    }
    // 获取自定义配置
    var options = $.extend(defaults, options);
    //获取页面宽高
    var _PageHeight = document.documentElement.clientHeight,
        _PageWidth = document.documentElement.clientWidth;

    //在页面未加载完毕之前显示的loading Html自定义内容
    var _LoadingHtml = '<div id="loadingPage" style="position:fixed;left:0;top:60px;_position: absolute;width:100%;height:'
        + _PageHeight + 'px;background:' + options.backgroundColor + ';opacity:' + options.opacity
        + ';filter:alpha(opacity=' + options.opacity * 100 + ');z-index:' + options.zindex
        + ';"><div id="loadingTips" style="position: absolute; cursor1: wait; width: auto;border-color:'
        + options.borderColor + ';border-style:' + options.borderStyle + ';border-width:' + options.borderWidth
        + 'px; height:80px; line-height:80px; padding-left:80px; padding-right: 5px;border-radius:10px;  background: '
        + options.backgroundColor + ' url(/images/loading.gif) no-repeat 5px;background-position:-50px center; color:'
        + options.TipsColor + ';font-size:20px;">' + options.loadingTips + '</div></div>';

    //呈现loading效果
    $("body").append(_LoadingHtml);

    //获取loading提示框宽高
    var _LoadingTipsH = document.getElementById("loadingTips").clientHeight,
        _LoadingTipsW = document.getElementById("loadingTips").clientWidth;

    //计算距离，让loading提示框保持在屏幕上下左右居中
    var _LoadingTop = _PageHeight > _LoadingTipsH ? (_PageHeight - _LoadingTipsH) / 2 - 60 : 0,
        _LoadingLeft = _PageWidth > _LoadingTipsW ? (_PageWidth - _LoadingTipsW) / 2 : 0;

    $("#loadingTips").css({
        "left": _LoadingLeft + "px",
        "top": _LoadingTop + "px"
    });
    //监听页面加载状态
    document.onreadystatechange = PageLoaded;
    //当页面加载完成后执行
    function PageLoaded() {
        if (document.readyState == "complete") {
            var loadingMask = $('#loadingPage');
            setTimeout(function () {
                loadingMask.animate({
                    "opacity": 0
                },
                    options.delayTime,
                    function () {
                        $(this).hide();

                    });
            }, options.sleep);
        }
    }
}
/**
 * 结束加载效果
 */
export function end() {
    $("#loadingPage").remove();
}
